// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "as370-thermal.h"

#include <fbl/algorithm.h>
#include <fuchsia/hardware/thermal/c/fidl.h>
#include <lib/fake_ddk/fidl-helper.h>
#include <mock-mmio-reg/mock-mmio-reg.h>
#include <mock/ddktl/protocol/clock.h>
#include <mock/ddktl/protocol/power.h>
#include <zxtest/zxtest.h>

namespace {

bool FloatNear(float a, float b) { return std::abs(a - b) < 0.001f; }

}  // namespace

namespace thermal {

using ThermalClient = llcpp::fuchsia::hardware::thermal::Device::SyncClient;
using llcpp::fuchsia::hardware::thermal::OperatingPoint;
using llcpp::fuchsia::hardware::thermal::OperatingPointEntry;
using llcpp::fuchsia::hardware::thermal::PowerDomain;
using llcpp::fuchsia::hardware::thermal::ThermalDeviceInfo;

class As370ThermalTest : public zxtest::Test {
 public:
  As370ThermalTest()
      : reg_region_(reg_array_, sizeof(uint32_t), fbl::count_of(reg_array_)),
        dut_(nullptr, ddk::MmioBuffer(reg_region_.GetMmioBuffer()), kThermalDeviceInfo,
             ddk::ClockProtocolClient(clock_.GetProto()),
             ddk::PowerProtocolClient(power_.GetProto())) {}

  void SetUp() {
    ASSERT_OK(messenger_.SetMessageOp(
        &dut_, [](void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) -> zx_status_t {
          return static_cast<As370Thermal*>(ctx)->DdkMessage(msg, txn);
        }));
  }

 protected:
  void VerifyAll() {
    ASSERT_NO_FATAL_FAILURES(clock_.VerifyAndClear());
    ASSERT_NO_FATAL_FAILURES(power_.VerifyAndClear());
  }

  fake_ddk::FidlMessenger messenger_;
  ddk_mock::MockMmioRegRegion reg_region_;
  ddk::MockClock clock_;
  ddk::MockPower power_;

 private:
  static constexpr ThermalDeviceInfo kThermalDeviceInfo = {
      .active_cooling = false,
      .passive_cooling = true,
      .gpu_throttling = false,
      .num_trip_points = 0,
      .big_little = false,
      .critical_temp_celsius = 0.0f,
      .trip_point_info = {},
      .opps =
          fidl::Array<OperatingPoint, 2>{
              OperatingPoint{
                  .opp =
                      fidl::Array<OperatingPointEntry, 16>{
                          // clang-format off
                          OperatingPointEntry{.freq_hz =   400'000'000, .volt_uv = 825'000},
                          OperatingPointEntry{.freq_hz =   800'000'000, .volt_uv = 825'000},
                          OperatingPointEntry{.freq_hz = 1'200'000'000, .volt_uv = 825'000},
                          OperatingPointEntry{.freq_hz = 1'400'000'000, .volt_uv = 825'000},
                          OperatingPointEntry{.freq_hz = 1'500'000'000, .volt_uv = 900'000},
                          OperatingPointEntry{.freq_hz = 1'800'000'000, .volt_uv = 900'000},
                          // clang-format on
                      },
                  .latency = 0,
                  .count = 6,
              },
              {
                  .opp = {},
                  .latency = 0,
                  .count = 0,
              },
          },
  };

  ddk_mock::MockMmioReg reg_array_[8];
  As370Thermal dut_;
};

TEST_F(As370ThermalTest, GetTemperature) {
  ThermalClient client(std::move(messenger_.local()));

  reg_region_[0x14].ReadReturns(0x17ff);
  auto result = client.GetTemperatureCelsius();
  EXPECT_OK(result->status);
  EXPECT_TRUE(FloatNear(result->temp, 40.314f));

  reg_region_[0x14].ReadReturns(0x182b);
  result = client.GetTemperatureCelsius();
  EXPECT_OK(result->status);
  EXPECT_TRUE(FloatNear(result->temp, 43.019f));
}

TEST_F(As370ThermalTest, DvfsOperatingPoint) {
  ThermalClient client(std::move(messenger_.local()));

  // Success, sets operating point 0.
  power_.ExpectRequestVoltage(ZX_OK, 825'000, 825'000);
  clock_.ExpectSetRate(ZX_OK, 400'000'000);
  auto set_result = client.SetDvfsOperatingPoint(0, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(set_result->status);
  ASSERT_NO_FATAL_FAILURES(VerifyAll());

  auto get_result = client.GetDvfsOperatingPoint(PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(get_result->status);
  EXPECT_EQ(get_result->op_idx, 0);

  // Failure, unable to set exact voltage.
  power_.ExpectRequestVoltage(ZX_OK, 825'000, 900'000);
  set_result = client.SetDvfsOperatingPoint(2, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_NOT_OK(set_result->status);
  ASSERT_NO_FATAL_FAILURES(VerifyAll());

  get_result = client.GetDvfsOperatingPoint(PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(get_result->status);
  EXPECT_EQ(get_result->op_idx, 0);

  // Failure, unable to set frequency.
  power_.ExpectRequestVoltage(ZX_OK, 825'000, 825'000);
  clock_.ExpectSetRate(ZX_ERR_IO, 1'200'000'000);
  set_result = client.SetDvfsOperatingPoint(2, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_NOT_OK(set_result->status);
  ASSERT_NO_FATAL_FAILURES(VerifyAll());

  get_result = client.GetDvfsOperatingPoint(PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(get_result->status);
  EXPECT_EQ(get_result->op_idx, 0);

  // Success, sets operating point 4.
  power_.ExpectRequestVoltage(ZX_OK, 900'000, 900'000);
  clock_.ExpectSetRate(ZX_OK, 1'500'000'000);
  set_result = client.SetDvfsOperatingPoint(4, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(set_result->status);
  ASSERT_NO_FATAL_FAILURES(VerifyAll());

  get_result = client.GetDvfsOperatingPoint(PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(get_result->status);
  EXPECT_EQ(get_result->op_idx, 4);

  // Failure, unable to set frequency.
  clock_.ExpectSetRate(ZX_ERR_IO, 800'000'000);
  set_result = client.SetDvfsOperatingPoint(1, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_NOT_OK(set_result->status);
  ASSERT_NO_FATAL_FAILURES(VerifyAll());

  get_result = client.GetDvfsOperatingPoint(PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(get_result->status);
  EXPECT_EQ(get_result->op_idx, 4);

  // Failure, unable to set voltage.
  clock_.ExpectSetRate(ZX_OK, 800'000'000);
  power_.ExpectRequestVoltage(ZX_ERR_IO, 825'000, 0);
  set_result = client.SetDvfsOperatingPoint(1, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_NOT_OK(set_result->status);
  ASSERT_NO_FATAL_FAILURES(VerifyAll());

  get_result = client.GetDvfsOperatingPoint(PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(get_result->status);
  EXPECT_EQ(get_result->op_idx, 4);

  // Success, sets operating point 1.
  clock_.ExpectSetRate(ZX_OK, 800'000'000);
  power_.ExpectRequestVoltage(ZX_OK, 825'000, 825'000);
  set_result = client.SetDvfsOperatingPoint(1, PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(set_result->status);
  ASSERT_NO_FATAL_FAILURES(VerifyAll());

  get_result = client.GetDvfsOperatingPoint(PowerDomain::BIG_CLUSTER_POWER_DOMAIN);
  EXPECT_OK(get_result->status);
  EXPECT_EQ(get_result->op_idx, 1);
}

}  // namespace thermal
